package org.sgmiller.quickstem;

import static org.sgmiller.quickstem.TransactionState.*;

class Version<V> {
	static final boolean STATS=false;
	
	Version<V> last;
	Transaction txn; 
	V value;
	
	Version(Transaction txn, V value, Version<V> last) {
		this.last=last;
		this.txn=txn;
		this.value=value;
	}
	
	/**
	 * Passes through the version list, pruning it to non-rolled back versions between now and the youngest
	 * committed transaction older than the oldest outstanding (non-completed) transaction.
	 * 
	 * Eg, if R=Rolled_back, C=Committed, O=Open:
	 * 
	 *            +- Most recent version
	 *            v
	 * CCCCCCRROCRO => CCCCOCO 
	 *    ^
	 *    +- oldest open transaction
	 * 
	 * @param lastOutstanding
	 * @return
	 */
	@SuppressWarnings("unchecked")
	Version<V> extend(final Transaction txn, final V value) {
		//Gather stats
		if (STATS) {
			runs++;
			Version c=this;
			while (c!=null) {
				versions++;
				switch (c.txn.state) {
				case OPEN:
					openversions++;
					break;
				case COMMITTED:
					commitedversions++;
					break;
				case ROLLED_BACK:
					rolledbackversions++;
					break;
				}
				c=c.last;
			}
		}
		
		long firstOutstanding = Transaction.getFirstOutstanding().tid;
	
		Version firstNotRolledBack = null;
		Version v=this;
		
		search:
		do {			
			switch(v.txn.state.ordinal) {
			case ORD_COMMITTED:
				if (v.txn.tid < firstOutstanding ) {
					v.last=null;
				} 
				if (firstNotRolledBack == null) {
					firstNotRolledBack=v;
				}
				//Optimization: if we're extending with a one shot transaction, and this
				//is an earlier one shot transaction, we just advance the older one's to this txn, and
				//update the value
				/*if (v.txn.getIsolationLevel()==IsolationLevel.ONE_SHOT && v.txn.tid<txn.tid) {
					v.txn=txn;
					((Version<V>)v).value=value;
					return firstNotRolledBack;
				}*/
				break;
			case ORD_OPEN:
				if (firstNotRolledBack == null) 
					firstNotRolledBack=v;

				//If this is our txn, go ahead and change the value in place
				if (v.txn == txn) {
					v.value = value;
					assert(firstNotRolledBack != null);
					return firstNotRolledBack;
				}
				break;
			}

			if (txn.isRelated(v.txn) && firstNotRolledBack != null) 
				//We *must* insert our own version in the list, so as to override the subtransaction's
				break search;

			v=v.last;
		} while (v!=null);

		return new Version(txn, value, firstNotRolledBack);
	}
	
	public String toString() {
		StringBuilder b=new StringBuilder("[");
		toString(b);
		b.append("]");
		return b.toString();
	}

	private void toString(StringBuilder b) {
		b.append(txn.toString()).append('=').append(value);
		if (last!=null) {
			b.append(", ");
			last.toString(b);
		}
	}
	
	/**
	 * Statistics collection
	 */
	static int runs, versions, openversions, commitedversions, rolledbackversions;

	static {
		if (STATS) {
			Runtime.getRuntime().addShutdownHook(new Thread() {
				public void run() {
					System.out.println("Runs: " + runs);
					System.out.println("Average versions: " + ((double)versions / runs));
					System.out.println("Average open versions: " + ((double)openversions / runs));
					System.out.println("Average commited versions: " + ((double)commitedversions / runs));
					System.out.println("Average rolled back versions: " + ((double)rolledbackversions / runs));
				}
			});
		}
	}
}
